package org.hepeng.workx.spring.cloud.netflix.zuul.route;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.hepeng.workx.spring.context.ApplicationContextHolder;
import org.hepeng.workx.web.http.BodyCachingHttpServletRequestWrapper;
import org.hepeng.workx.web.util.HttpRequestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.util.Assert;
import org.tuckey.web.filters.urlrewrite.extend.RewriteMatch;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * @author he peng
 */
public abstract class AbstractZuulRoutingRewriteMatch extends RewriteMatch {

    private static final Logger LOG = LoggerFactory.getLogger(AbstractZuulRoutingRewriteMatch.class);
    public static final String TARGET_SERVICE_REQUEST_ATTR = AbstractZuulRoutingRewriteMatch.class.getName() + ".target.service.name";

    protected HttpServletRequest request;
    protected String originalUri;
    protected List<Route> routes;

    public AbstractZuulRoutingRewriteMatch(HttpServletRequest request , String originalUri, List<Route> routes) {
        Assert.notNull(request, "request must not be null");
        Assert.hasLength(originalUri, "originalUri must not be blank");
        Assert.notNull(routes, "routes must not be null");

        this.request = request;
        this.originalUri = originalUri;
        this.routes = routes;
    }

    @Override
    public String getMatchingUrl() {
        String matchingUrl = null;
        if (CollectionUtils.isNotEmpty(routes)) {
            String appId = getApplicationId();
            appId = StringUtils.isBlank(appId) ? "" : appId;
            String contextPath = this.request.getServletContext().getContextPath();
            for (Route route : routes) {
                if (matchRoute(route , appId)) {
                    String prefix = route.getPrefix();
                    if (! StringUtils.startsWith(prefix , "/")) {
                        prefix += "/";
                    }
                    matchingUrl = contextPath + prefix + StringUtils.removeFirst(this.originalUri , contextPath);
                    this.request.setAttribute(TARGET_SERVICE_REQUEST_ATTR , route.getId());
                    break;
                }
            }
        }
        return matchingUrl;
    }

    protected abstract boolean matchRoute(Route route , String appId);

    protected abstract String getApplicationId();

    @Override
    public boolean execute(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String matchingUrl = getMatchingUrl();
        if (StringUtils.isNotBlank(matchingUrl)) {
            if (HttpRequestUtils.isMultipart(request)) {
                ZuulProperties zuulProperties = ApplicationContextHolder.getApplicationContext().getBean(ZuulProperties.class);
                if (! StringUtils.startsWith(matchingUrl , zuulProperties.getServletPath())) {
                    matchingUrl = zuulProperties.getServletPath() + matchingUrl;
                }
                request = new BodyCachingHttpServletRequestWrapper(request);
            }
            LOG.info("after rewriting request uri -> {} , do request forward " , matchingUrl);
            RequestDispatcher dispatcher = request.getRequestDispatcher(matchingUrl);
            dispatcher.forward(request , response);
            return true;
        } else {
            LOG.info("No match to the path that needs to be rewritten");
            return false;
        }
    }
}
